React Components
React allows you to break a UI down to independent, reusable chunks - components.
Example: The following website could be broken into the following components:
App, which represents your main application and will be the parent of all other components.Navbar, which will be the navigation bar.MainArticle, which will be the component that renders your main content.NewsletterForm, which is a form that lets a user input their email to receive the weekly newsletter.
I. Create Components
1. JSX
React components can be written in both regular JavaScript and JSX, but JSX makes writing components much more intuitive and readable. JSX is a syntax extension for JavaScript that lets you write HTML-like markup inside a JavaScript file.
React projects defaults to JSX.
- When using pure JavaScript, we use the React createElement function. This function creates a React element, which is a plain object.
// Without JSX (Pure JavaScript)
function Welcome(props) {
return React.createElement(
'div',
null,
'Hello, ',
props.name
);
}
// With JSX
function Welcome(props) {
return (
<div>
Hello, {props.name}
</div>
);
}
Under the hood, JSX actually gets converted into regular JavaScript (like the first example) during the build process. This conversion is handled by a tool called Babel.
When you create a new React project (whether with Vite or another tool), it automatically includes Babel.
- Babel knows how to handle JSX regardless of the file extension. This is why you'll see both
.jsand.jsxfiles containing JSX code in React projects.- Using
.jsxfor files containing JSX has become a common convention because it makes it immediately clear that the file contains JSX code.
JSX Syntax and Rules
Resources
1. Return a single root element
A component returns a single root element. To return multiple elements in a component, we can either:
- Wrap them in a parent tag. e.g.
<div> - A React fragment
<> </>
- Correct Syntax:
function App() {
// Could replace <></> with <div></div>
return (
<>
<h1>Example h1</h1>
<h2>Example h2</h2>
</>
);
}
- Incorrect Syntax:
function App() {
return (
<h1>Example h1</h1>
<h2>Example h2</h2>
);
}
2. Close ALL tags
In HTML, many tags are self-closing and self-wrapping. In JSX however, we must explicitly close and wrap these tags.
Example:- Correct Syntax:
function App() {
return (
<>
<input />
<li></li>
</>
);
}
- Incorrect Syntax:
function App() {
return (
<>
<input>
<li>
</>
);
}
3. camelCase attributes
JSX turns into JavaScript, and attributes of elements become keys of JavaScript objects, so you can’t use dashes or reserved words such as class.
- Correct Syntax:
function App() {
return (
<div className="container">
<svg>
<circle cx="25" cy="75" r="20" stroke="green" strokeWidth="2" />
</svg>
</div>
);
}
- Incorrect Syntax:
function App() {
return (
<div class="container">
<svg>
<circle cx="25" cy="75" r="20" stroke="green" stroke-width="2" />
</svg>
</div>
);
}
2. React Module System - export & import
React applications are built by combining multiple components, which are typically stored in separate files. To make these components work together, we need to understand JavaScript's module system, which uses import and export statements.
There are two main ways to export components in React:
// Default Export (UserProfile.jsx)
function UserProfile() {
return <div>User Profile Component</div>;
}
export default UserProfile; // Only one default export per file
// Named Export (Button.jsx)
export function PrimaryButton() {
return <button className="primary">Click Me</button>;
}
export function SecondaryButton() {
return <button className="secondary">Click Me</button>;
}
When importing these components, we use corresponding import syntax:
- File extensions (
.jsxor.js) are optional in imports.
// App.jsx
import UserProfile from './UserProfile'; // Default import
import { PrimaryButton, SecondaryButton } from './Button'; // Named imports
function App() {
return (
<div>
<UserProfile />
<PrimaryButton />
<SecondaryButton />
</div>
);
}
The name of the component in React should be capitalized. This is because when JSX is parsed, React uses capitalization to tell the difference between an HTML tag and an instance of a React component. e.g.
Buttoncomponent vs.buttontag.
II. Rendering Components
1. JSX and JavaScript Expressions
Resources:
Curly braces {} in React create a "window" into JavaScript from within JSX, allowing you to embed expressions and dynamic values in your markup.
You can use curly braces in several ways:
1. For displaying variables
function Greeting() {
const name = "Sara";
return <h1>Hello, {name}!</h1>;
}
2. For expressions and calculations
<p>Total: ${price + (price * taxRate)}</p>
3. For component attributes/props:
<img src={imageUrl} alt="Profile" />
<Button disabled={!isVerified}>Edit Profile</Button>
4. For inline styles (using double curly braces):
<p style={{ color: "blue", fontSize: "16px" }}>Styled text</p>
5. For [[#2. Conditional Rendering|conditional rendering]]
<div>
{isLoggedIn ? <WelcomeMessage /> : <LoginButton />}
{hasMessages && <NotificationIcon />}
</div>
2. Rendering Lists
Resources:
a. Basic List Rendering
function App() {
return (
<div>
<h1>Animals: </h1>
<ul>
<li>Lion</li>
<li>Cow</li>
<li>Snake</li>
<li>Lizard</li>
</ul>
</div>
);
}
Instead of manually writing multiple elements like this, we can use JavaScript's map() to render lists. JSX can automatically render arrays of elements
1. Direct Mapping
In this example, both the key and the content of the <li> are using the same value from the animal variable in the map() function.
function App() {
const animals = ["Lion", "Cow", "Snake", "Lizard"];
return (
<div>
<ul>
{animals.map((animal) => <li key={animal}>{animal}</li>)}
</ul>
</div>
);
}
// OUTPUT
// First iteration
<li key="Lion">Lion</li>
// Second iteration
<li key="Cow">Cow</li>
// Third iteration
<li key="Snake">Snake</li>
// Fourth iteration
<li key="Lizard">Lizard</li>
Important Note: When using
map()for rendering, each iteration must return a React element. The arrow function above implicitly returns the<li>element.
2. Separate Variable
Creating a separate variable to hold your mapped elements can make your JSX cleaner and allow you to transform your list before rendering.
In this approach, we process the array with map() first, storing the resulting array of React elements in a variable, and then include that variable in our JSX return statement.
function App() {
const animals = ["Lion", "Cow", "Snake", "Lizard"];
const animalsList = animals.map((animal) => <li key={animal}>{animal}</li>);
return (
<div>
<ul>{animalsList}</ul>
</div>
);
}
b. Component-Based List Rendering
Example:
The App component is our top-level component (parent). It contains our source data - an array of animals: ["Lion", "Cow", "Snake", "Lizard"].
- The
Appcomponent passes this array to theListcomponent through what we call a "[[02. React Components#IV. Props |prop]]" (short for property). When we write<List animals={animals} />, we're passing theanimalsarray as as prop named “animals” to theListcomponent.
// top-level component - parent
function App() {
const animals = ["Lion", "Cow", "Snake", "Lizard"]; // source data
// passes the array to List component
return (
<div>
<h1>Animals: </h1>
<List animals={animals} />
</div>
);
}
The List component receives these props and processes them:
- It creates the
<ul>(unordered list) container - It maps over each animal in the array, creating a
ListItemcomponent for each one. For each animal, it passes two props:key: A special React prop used for optimization (required when mapping)animal: The actual animal string that we want to display
// This is what props looks like inside the List component
props = {
animals: ["Lion", "Cow", "Snake", "Lizard"]
}
function List(props) {
return (
<ul>
{props.animals.map((animal) => {
return <ListItem key={animal} animal={animal} />;
})}
</ul>
);
}
The ListItem component just takes the animal it received as a prop and displays it inside an <li> (list item) tag.
// This is what props looks like inside the ListItem component
props = {
animal: "Lion",
// Note: key is a special prop that React uses internally
// and isn't actually accessible inside props
}
function ListItem(props) {
return <li>{props.animal}</li>
}
2. Conditional Rendering
Resources
a. Ternary Operator
One way to conditionally render an element is with a ternary operator, using a boolean value to decide what to render.
Example:
We are using the String method startsWith to check if the animal starts with the letter L. This method either returns true or false.
function List(props) {
return (
<ul>
{props.animals.map((animal) => {
return animal.startsWith("L") ? <li key={animal}>{animal}</li> : null;
})}
</ul>
);
}
function App() {
const animals = ["Lion", "Cow", "Snake", "Lizard"];
return (
<div>
<h1>Animals: </h1>
<List animals={animals} />
</div>
);
}
b.. && operator
When using
&&for conditional rendering, don’t put numbers on the left side.
Example:
If the result of the startsWith function is true, then it returns the second operand, which is the <li> element, and renders it. Otherwise, if the condition is false it just gets ignored.
function List(props) {
return (
<ul>
{props.animals.map((animal) => {
return animal.startsWith("L") && <li key={animal}>{animal}</li>;
})}
</ul>
);
}
function App() {
const animals = ["Lion", "Cow", "Snake", "Lizard"];
return (
<div>
<h1>Animals: </h1>
<List animals={animals} />
</div>
);
}
c. Conditional Statements
We can also use if, if/else, and switch to conditionally render something.
- Check if the
animalsproperty exists - Check if the
animalslength is greater than 0
function List(props) {
if (!props.animals) {
return <div>Loading...</div>;
}
if (props.animals.length === 0) {
return <div>There are no animals in the list!</div>;
}
return (
<ul>
{props.animals.map((animal) => {
return <li key={animal}>{animal}</li>;
})}
</ul>
);
}
function App() {
const animals = [];
return (
<div>
<h1>Animals: </h1>
<List animals={animals} />
</div>
);
}
III. Keys
Resource
Keys are special identifiers that React uses to track individual elements in a list. They are not a normal prop that gets passed down to the component to use, it's a special attribute that React uses internally to keep track of elements, similar to how HTML elements have attributes like id or class.
// SYNTAX
<Component key={keyValue} />
// or
<div key={keyValue} />
function BookList() {
const books = [
{ id: "book_1", title: "React 101" },
{ id: "book_2", title: "JavaScript Basics" }
];
return (
<ul>
{books.map(book => (
// The key is like a sticker on the li element
// React uses this internally, but the ListItem component
// never sees or knows about this key
<li key={book.id}>
{/* Props are passed as data to the component */}
<ListItem title={book.title} />
</li>
))}
</ul>
);
}
When React updates this list, it uses those keys to efficiently track which elements need to change.
1. Rules and Best Practices
Keys must be unique among siblings (but can be repeated in different lists). If you are defining data yourself, it is good practice to assign a unique id to each item. You can use the crypto.randomUUID() function to generate a unique id.
// a list of todos, each todo object has a task and an id
const todos = [
{ task: "mow the yard", id: crypto.randomUUID() },
{ task: "Work on Odin Projects", id: crypto.randomUUID() },
{ task: "feed the cat", id: crypto.randomUUID() },
];
function TodoList() {
return (
<ul>
{todos.map((todo) => (
// here we are using the already generated id as the key.
<li key={todo.id}>{todo.task}</li>
))}
</ul>
);
}
2. Anti-pattern
Keys should never be generated on the fly. Using key={Math.random()} or key={crypto.randomUUID()} while rendering the list defeats the purpose of the key. They’re no longer consistent as now a new key will get created for every render of the list.
→ The key should be inferred from the data itself.
const todos = [
{ task: "mow the yard", id: crypto.randomUUID() },
{ task: "Work on Odin Projects", id: crypto.randomUUID() },
{ task: "feed the cat", id: crypto.randomUUID() },
];
function TodoList() {
return (
<ul>
{todos.map((todo) => (
// DON'T do the following i.e. generating keys during render
<li key={crypto.randomUUID()}>{todo.task}</li>
))}
</ul>
);
}
IV. Props
Resource
Props (short for properties) are a way to pass data from parent components to child components in React. They are read-only, meaning child components cannot modify the props they receive.
This data transfer is unidirectional, meaning it flows in only one direction (parent → child). Any changes made to this data will only affect child components using the data, and not parent or sibling components. If you need to modify data, it should be done in the parent component
// Basic syntax for passing props
<ComponentName propName={propValue} />
// Basic syntax for receiving props
function ComponentName(props) {
return <div>{props.propName}</div>;
}
// Alternate syntax using destructuring
function ComponentName({ propName }) {
return <div>{propName}</div>;
}
Props can be of any data type:
<MyComponent
string="Hello" // String
number={42} // Number
boolean={true} // Boolean
array={[1, 2, 3]} // Array
object={{ key: "value" }} // Object
function={() => {}} // Function
/>
1. Basic Uses of Props
By using props, we account for any number of variations with a single component. This allows one component to become more versatile.
Example:- The
Buttonfunctional component now receivespropsas a function argument. The individual properties are then referenced within the component viaprops.propertyName. - When rendering the
Buttoncomponents withinApp, thepropvalues are defined on each component. - Inline styles are dynamically generated and then applied to the
buttonelement.
function Button(props) {
const buttonStyle = {
color: props.color,
fontSize: props.fontSize + 'px'
};
return (
<button style={buttonStyle}>{props.text}</button>
);
}
export default function App() {
return (
<div>
<Button text="Click Me!" color="blue" fontSize={12} />
<Button text="Don't Click Me!" color="red" fontSize={12} />
<Button text="Click Me!" color="blue" fontSize={20} />
</div>
);
}
2. Prop Destructuring
A common pattern you will come across in React is prop destructuring. Unpacking your props in the component arguments allows for more concise and readable code.
Prop Structure
Props in React are passed as a single JavaScript object. When you write:
<Button
text="Click Me!"
color="blue"
fontSize={12}
/>
React internally combines all these attributes into a single props object that looks like:
{
text: 'Click Me!',
color: 'blue',
fontSize = {12}
}
This props object is then passed as the first argument to your function component. That's why you can destructure it with:
function Button({ text, color, fontSize }) {
// ...
}
Or access the properties using the props parameter:
function InfoSection(props) {
// Access as props.data and props.onTextChange
}
function Button({ text, color, fontSize }) {
const buttonStyle = {
color: color,
fontSize: fontSize + "px"
};
return <button style={buttonStyle}>{text}</button>;
}
export default function App() {
return (
<div>
<Button text="Click Me!" color="blue" fontSize={12} />
<Button text="Don't Click Me!" color="red" fontSize={12} />
<Button text="Click Me!" color="blue" fontSize={20} />
</div>
);
}
3. Default Props
We can define default parameters to set default values for props. This can help protect your application from undefined values and also avoid code repetition when creating components.
Example:
We now only need to supply prop values to Button when rendering within App if they differ from the default values defined in the function parameters.
function Button({ text = "Click Me!", color = "blue", fontSize = 12 }) {
const buttonStyle = {
color: color,
fontSize: fontSize + "px"
};
return <button style={buttonStyle}>{text}</button>;
}
export default function App() {
return (
<div>
<Button />
<Button text="Don't Click Me!" color="red" />
<Button fontSize={20} />
</div>
);
}
You may also come across the use of defaultProps in some codebases. This was traditionally used to set default values for props, particularly in class components. While React now prefers the default parameter approach for function components, understanding defaultProps is still useful, especially when working with class components or older codebases.
function Button({ text, color, fontSize }) {
const buttonStyle = {
color: color,
fontSize: fontSize + "px"
};
return <button style={buttonStyle}>{text}</button>;
}
// defaultProps!!!
Button.defaultProps = {
text: "Click Me!",
color: "blue",
fontSize: 12
};
export default function App() {
return (
<div>
<Button />
<Button text="Don't Click Me!" color="red" />
<Button fontSize={20} />
</div>
);
}
4. Functions as props
In addition to passing variables through to child components as props, you can also pass through functions.
Example:- The function
handleButtonClickis defined in the parentAppcomponent. - A reference to this function is passed through as the value for the
handleClickprop on theButtoncomponent. - The function is received in
Buttonand is called on a click event.
function Button({ text = "Click Me!", color = "blue", fontSize = 12, handleClick }) {
const buttonStyle = {
color: color,
fontSize: fontSize + "px"
};
return (
<button onClick={handleClick} style={buttonStyle}>
{text}
</button>
);
}
export default function App() {
const handleButtonClick = () => {
window.location.href = "https://www.google.com";
};
return (
<div>
<Button handleClick={handleButtonClick} />
</div>
);
}
Similar with event handlers, we only pass through a reference to
handleButtonClick, not the actual functionhandleButtonClick()because we don’t want to call the function right away, but call it only when the button is clicked.